package com.ruoyi.common.security.exception;

import com.ruoyi.common.core.exception.BaseException;
import com.ruoyi.common.core.utils.HttpRequestUtil;
import com.ruoyi.common.redis.service.RedisService;
import com.ruoyi.common.security.config.properties.SecurityProperties;
import com.ruoyi.common.security.constant.RedisNameEnum;
import com.ruoyi.common.security.domain.LoginUser;
import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.system.api.model.dto.SysLoginLogDto;
import com.ruoyi.system.api.service.ILogApiService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.DefaultThrowableAnalyzer;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InsufficientScopeException;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.web.util.ThrowableAnalyzer;
import org.springframework.stereotype.Service;
import org.springframework.web.HttpRequestMethodNotSupportedException;

import java.util.concurrent.TimeUnit;

/**
 * OAuth2 自定义异常处理
 *
 * @author ruoyi
 */
@Slf4j
@Service
public class CustomWebResponseExceptionTranslator implements WebResponseExceptionTranslator<OAuth2Exception> {

    @Autowired
    public RedisService redisService;
    @Autowired
    public SecurityProperties securityProperties;

    private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();

    @Autowired
    ILogApiService logApiService;

    @Override
    public ResponseEntity<OAuth2Exception> translate(Exception e) {

        Throwable[] causeChain = throwableAnalyzer.determineCauseChain(e);
        Exception ase = (OAuth2Exception) throwableAnalyzer.getFirstThrowableOfType(OAuth2Exception.class,
                causeChain);

        if (ase != null) {
            return handleOAuth2Exception((OAuth2Exception) ase);
        }

        ase = (BaseException) throwableAnalyzer.getFirstThrowableOfType(
                BaseException.class, causeChain);
        if (ase != null) {
            BaseException baseException = (BaseException) ase;
            BusinessOAuth2Exception businessOAuth2Exception = new BusinessOAuth2Exception(baseException);
            //伪造异常
            /*if(RuoyiSecurityException.RUOYI_SECURITY_USER_NOT_FOUND.getCode().equals(baseException.getCode())){
                return handleOAuth2Exception(new InvalidGrantException("用户名或密码错误"));
            }else {
                return handleOAuth2Exception(businessOAuth2Exception, ((BaseException) ase).getData());
            }*/
            return handleOAuth2Exception(businessOAuth2Exception, baseException.getData());
        }

        ase = (AuthenticationException) throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class,
                causeChain);
        if (ase != null) {
            return handleOAuth2Exception(new CustomWebResponseExceptionTranslator.UnauthorizedException(e.getMessage(), e));
        }

        ase = (AccessDeniedException) throwableAnalyzer
                .getFirstThrowableOfType(AccessDeniedException.class, causeChain);
        if (ase instanceof AccessDeniedException) {
            return handleOAuth2Exception(new CustomWebResponseExceptionTranslator.ForbiddenException(ase.getMessage(), ase));
        }

        ase = (HttpRequestMethodNotSupportedException) throwableAnalyzer.getFirstThrowableOfType(
                HttpRequestMethodNotSupportedException.class, causeChain);
        if (ase instanceof HttpRequestMethodNotSupportedException) {
            return handleOAuth2Exception(new CustomWebResponseExceptionTranslator.MethodNotAllowed(ase.getMessage(), ase));
        }


        return handleOAuth2Exception(new CustomWebResponseExceptionTranslator.ServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), e));
    }

    private ResponseEntity<OAuth2Exception> handleOAuth2Exception(OAuth2Exception e) {
        return handleOAuth2Exception(e, null);
    }

    private ResponseEntity<OAuth2Exception> handleOAuth2Exception(OAuth2Exception e, Object data) {
        int status = e.getHttpErrorCode();
        HttpHeaders headers = new HttpHeaders();
        headers.set("Cache-Control", "no-store");
        headers.set("Pragma", "no-cache");
        if (status == HttpStatus.UNAUTHORIZED.value() || (e instanceof InsufficientScopeException)) {
            headers.set("WWW-Authenticate", String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, e.getSummary()));
        }

        // 使用自定义扩展OAuth2Exception
        CustomOAuth2Exception ce = new CustomOAuth2Exception(e.getMessage(), e.getOAuth2ErrorCode(), data, e);
        ce = handleLoginException(ce);
        ResponseEntity<OAuth2Exception> response = new ResponseEntity<OAuth2Exception>(ce, headers, HttpStatus.valueOf(status));
        return response;
    }

    /**
     * 处理登录异常
     * @param customOAuth2Exception
     * @return
     */
    private CustomOAuth2Exception handleLoginException(CustomOAuth2Exception customOAuth2Exception){
        try {
            if (CustomOAuth2Exception.INVALID_GRANT.equals(customOAuth2Exception.getOAuth2ErrorCode())||
                    RuoyiSecurityException.RUOYI_SECURITY_USER_NOT_FOUND.getCode().equals(customOAuth2Exception.getOAuth2ErrorCode())) {
                LoginUser loginUser = SecurityUtils.loginUserLocal.get();
                SysLoginLogDto sysLoginLog = new SysLoginLogDto();
                sysLoginLog.setUserId(loginUser.getUserId());
                sysLoginLog.setUserName(loginUser.getUsername());
                sysLoginLog.setStatus("1");
                sysLoginLog.setIpAddr(HttpRequestUtil.getAddr());
                sysLoginLog.setBrowser(HttpRequestUtil.getBrowser());
                sysLoginLog.setOs(HttpRequestUtil.getOs());

                //登录失败日志
                if (CustomOAuth2Exception.INVALID_GRANT.equals(customOAuth2Exception.getOAuth2ErrorCode())) {
                    sysLoginLog.setMsg("登录失败:用户名密码错误");
                } else if (RuoyiSecurityException.RUOYI_SECURITY_USER_NOT_FOUND.getCode().equals(customOAuth2Exception.getOAuth2ErrorCode())) {
                    sysLoginLog.setMsg("登录失败:未找到用户");
                    //当用户为未找到的时候 返回用户名密码错误
                    InvalidGrantException invalidGrantException = new InvalidGrantException("用户密码错误");
                    customOAuth2Exception = new CustomOAuth2Exception(invalidGrantException.getMessage(), invalidGrantException.getOAuth2ErrorCode(), null, null);
                } else {
                    sysLoginLog.setMsg("登录失败:" + customOAuth2Exception.getMessage());
                }
                if (securityProperties.getLoginAllowErrorQuantity() > 0) {
                    if (loginUser != null && loginUser.getUserId() != null) {
                        String loginErrorQuantityName = RedisNameEnum.loginErrorQuantity.name() + "_" + loginUser.getUserId();
                        Integer loginErrorQuantity = redisService.getCacheObject(loginErrorQuantityName);
                        loginErrorQuantity = loginErrorQuantity == null ? 0 : loginErrorQuantity;
                        ++loginErrorQuantity;
                        if (loginErrorQuantity < securityProperties.getLoginAllowErrorQuantity()) {
                            redisService.setCacheObject(loginErrorQuantityName, loginErrorQuantity, securityProperties.getLoginErrorQuantityTime(), TimeUnit.SECONDS);
                        } else {
                            //停掉用户
//                    userApiService.stopUser(loginUser.getUserId());
                            log.info("{}用户需要做停账户的操作:", loginUser.getUsername());
                            redisService.deleteObject(loginErrorQuantityName);
                        }
                        log.info("loginErrorQuantity:" + loginErrorQuantity);
                    }
                }

                logApiService.saveLoginLog(sysLoginLog);
            }
        } catch (Exception e){
            log.error("处理异常登录失败：",e);
        }


        return customOAuth2Exception;

    }


    @SuppressWarnings("serial")
    private static class ForbiddenException extends OAuth2Exception {

        public ForbiddenException(String msg, Throwable t) {
            super(msg, t);
        }

        @Override
        public String getOAuth2ErrorCode() {
            return "access_denied";
        }

        @Override
        public int getHttpErrorCode() {
            return 403;
        }

    }

    @SuppressWarnings("serial")
    private static class ServerErrorException extends OAuth2Exception {

        public ServerErrorException(String msg, Throwable t) {
            super(msg, t);
        }

        @Override
        public String getOAuth2ErrorCode() {
            return "server_error";
        }

        @Override
        public int getHttpErrorCode() {
            return 500;
        }

    }

    @SuppressWarnings("serial")
    private static class UnauthorizedException extends OAuth2Exception {

        public UnauthorizedException(String msg, Throwable t) {
            super(msg, t);
        }

        @Override
        public String getOAuth2ErrorCode() {
            return "unauthorized";
        }

        @Override
        public int getHttpErrorCode() {
            return 401;
        }

    }

    @SuppressWarnings("serial")
    private static class MethodNotAllowed extends OAuth2Exception {

        public MethodNotAllowed(String msg, Throwable t) {
            super(msg, t);
        }

        @Override
        public String getOAuth2ErrorCode() {
            return "method_not_allowed";
        }

        @Override
        public int getHttpErrorCode() {
            return 405;
        }

    }


    @SuppressWarnings("serial")
    private static class BusinessOAuth2Exception extends OAuth2Exception {

        private BaseException baseException;

        public BusinessOAuth2Exception(BaseException baseException) {
            super(baseException.getBusinessMessage(), baseException);
            this.baseException = baseException;
        }

        @Override
        public String getOAuth2ErrorCode() {
            return baseException.getCode();
        }

        @Override
        public int getHttpErrorCode() {
            // The spec says this is a bad request (not unauthorized)
            return baseException.getStatus();
        }

    }
}
